﻿// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.


using System;
using System.Collections.Generic;
using System.Diagnostics;
using WpfInk.PresentationCore.System.Windows;
using WpfInk.PresentationCore.System.Windows.Ink;
using WpfInk.WindowsBase.System.Windows.Media;

namespace MS.Internal.Ink;

/// <summary>
/// StrokeNodeOperations implementation for elliptical nodes
/// </summary>
internal class EllipticalNodeOperations : StrokeNodeOperations
{
    /// <summary>
    /// Constructor
    /// </summary>
    /// <param name="nodeShape"></param>
    internal EllipticalNodeOperations(StylusShape nodeShape)
        : base(nodeShape)
    {
        System.Diagnostics.Debug.Assert((nodeShape != null) && nodeShape.IsEllipse);

        _radii = new Size(nodeShape.Width * 0.5, nodeShape.Height * 0.5);

        // All operations with ellipses become simple(r) if transfrom ellipses into circles.
        // Use the max of the radii for the radius of the circle
        _radius = Math.Max(_radii.Width, _radii.Height);

        // Compute ellipse-to-circle and circle-to-elliipse transforms. The former is used
        // in hit-testing operations while the latter is used when computing vertices of
        // a quadrangle connecting two ellipses
        _transform = nodeShape.Transform;
        _nodeShapeToCircle = _transform;

        Debug.Assert(_nodeShapeToCircle.HasInverse, "About to invert a non-invertible transform");
        _nodeShapeToCircle.Invert();
        if (DoubleUtil.AreClose(_radii.Width, _radii.Height))
        {
            _circleToNodeShape = _transform;
        }
        else
        {
            // Reverse the rotation
            if (false == DoubleUtil.IsZero(nodeShape.Rotation))
            {
                _nodeShapeToCircle.Rotate(-nodeShape.Rotation);
                Debug.Assert(_nodeShapeToCircle.HasInverse, "Just rotated an invertible transform and produced a non-invertible one");
            }

            // Scale to enlarge
            double sx, sy;
            if (_radii.Width > _radii.Height)
            {
                sx = 1;
                sy = _radii.Width / _radii.Height;
            }
            else
            {
                sx = _radii.Height / _radii.Width;
                sy = 1;
            }
            _nodeShapeToCircle.Scale(sx, sy);
            Debug.Assert(_nodeShapeToCircle.HasInverse, "Just scaled an invertible transform and produced a non-invertible one");

            _circleToNodeShape = _nodeShapeToCircle;
            _circleToNodeShape.Invert();
        }
    }

    /// <summary>
    /// This is probably not the best (design-wise) but the cheapest way to tell
    /// EllipticalNodeOperations from all other implementations of node operations.
    /// </summary>
    internal override bool IsNodeShapeEllipse { get { return true; } }

    /// <summary>
    /// Finds connecting points for a pair of stroke nodes
    /// </summary>
    /// <param name="beginNode">a node to connect</param>
    /// <param name="endNode">another node, next to beginNode</param>
    /// <returns>connecting quadrangle</returns>
    internal override Quad GetConnectingQuad(in StrokeNodeData beginNode,in StrokeNodeData endNode)
    {
        if (beginNode.IsEmpty || endNode.IsEmpty || DoubleUtil.AreClose(beginNode.Position, endNode.Position))
        {
            return Quad.Empty;
        }

        // Get the vector between the node positions
        Vector spine = endNode.Position - beginNode.Position;
        if (_nodeShapeToCircle.IsIdentity == false)
        {
            spine = _nodeShapeToCircle.Transform(spine);
        }

        double beginRadius = _radius * beginNode.PressureFactor;
        double endRadius = _radius * endNode.PressureFactor;

        // Get the vector and the distance between the node positions
        double distanceSquared = spine.LengthSquared;
        double delta = endRadius - beginRadius;
        double deltaSquared = DoubleUtil.IsZero(delta) ? 0 : (delta * delta);

        if (DoubleUtil.LessThanOrClose(distanceSquared, deltaSquared))
        {
            // One circle is contained within the other
            return Quad.Empty;
        }

        // Thus, at this point, distance > 0, which avoids the DivideByZero error
        // Also, here, distanceSquared > deltaSquared
        // Thus, 0 <= rSin < 1
        // Get the components of the radius vectors
        double distance = Math.Sqrt(distanceSquared);

        spine /= distance;

        Vector rad = spine;

        // Turn left
        double temp = rad.Y;
        rad.Y = -rad.X;
        rad.X = temp;

        Vector vectorToLeftTangent, vectorToRightTangent;
        double rSinSquared = deltaSquared / distanceSquared;

        if (DoubleUtil.IsZero(rSinSquared))
        {
            vectorToLeftTangent = rad;
            vectorToRightTangent = -rad;
        }
        else
        {
            rad *= Math.Sqrt(1 - rSinSquared);
            spine *= Math.Sqrt(rSinSquared);
            if (beginNode.PressureFactor < endNode.PressureFactor)
            {
                spine = -spine;
            }

            vectorToLeftTangent = spine + rad;
            vectorToRightTangent = spine - rad;
        }

        // Get the common tangent points

        if (_circleToNodeShape.IsIdentity == false)
        {
            vectorToLeftTangent = _circleToNodeShape.Transform(vectorToLeftTangent);
            vectorToRightTangent = _circleToNodeShape.Transform(vectorToRightTangent);
        }

        return new Quad(beginNode.Position + (vectorToLeftTangent * beginRadius),
            endNode.Position + (vectorToLeftTangent * endRadius),
            endNode.Position + (vectorToRightTangent * endRadius),
            beginNode.Position + (vectorToRightTangent * beginRadius));
    }

    /// <summary>
    ///
    /// </summary>
    /// <param name="node"></param>
    /// <param name="quad"></param>
    /// <returns></returns>
    internal override IEnumerable<ContourSegment> GetContourSegments(StrokeNodeData node, Quad quad)
    {
        System.Diagnostics.Debug.Assert(node.IsEmpty == false);

        if (quad.IsEmpty)
        {
            Point point = node.Position;
            point.X += _radius;
            yield return new ContourSegment(point, point, node.Position);
        }
        else if (_nodeShapeToCircle.IsIdentity)
        {
            yield return new ContourSegment(quad.A, quad.B);
            yield return new ContourSegment(quad.B, quad.C, node.Position);
            yield return new ContourSegment(quad.C, quad.D);
            yield return new ContourSegment(quad.D, quad.A);
        }
    }

    /// <summary>
    /// ISSUE-2004/06/15- temporary workaround to avoid hit-testing ellipses with ellipses
    /// </summary>
    /// <param name="beginNode"></param>
    /// <param name="endNode"></param>
    /// <returns></returns>
    internal override IEnumerable<ContourSegment> GetNonBezierContourSegments(StrokeNodeData beginNode, StrokeNodeData endNode)
    {
        Quad quad = beginNode.IsEmpty ? Quad.Empty : base.GetConnectingQuad(beginNode, endNode);
        return base.GetContourSegments(endNode, quad);
    }


    /// <summary>
    /// Hit-tests a stroke segment defined by two nodes against a linear segment.
    /// </summary>
    /// <param name="beginNode">Begin node of the stroke segment to hit-test. Can be empty (none)</param>
    /// <param name="endNode">End node of the stroke segment</param>
    /// <param name="quad">Pre-computed quadrangle connecting the two nodes.
    /// Can be empty if the begion node is empty or when one node is entirely inside the other</param>
    /// <param name="hitBeginPoint">an end point of the hitting linear segment</param>
    /// <param name="hitEndPoint">an end point of the hitting linear segment</param>
    /// <returns>true if the hitting segment intersect the contour comprised of the two stroke nodes</returns>
    internal override bool HitTest(
        in StrokeNodeData beginNode, in StrokeNodeData endNode, Quad quad, Point hitBeginPoint, Point hitEndPoint)
    {
        StrokeNodeData bigNode, smallNode;
        if (beginNode.IsEmpty || (quad.IsEmpty && (endNode.PressureFactor > beginNode.PressureFactor)))
        {
            // Need to test one node only
            bigNode = endNode;
            smallNode = StrokeNodeData.Empty;
        }
        else
        {
            // In this case the size doesn't matter.
            bigNode = beginNode;
            smallNode = endNode;
        }

        // Compute the positions of the involved points relative to bigNode.
        Vector hitBegin = hitBeginPoint - bigNode.Position;
        Vector hitEnd = hitEndPoint - bigNode.Position;

        // If the node shape is an ellipse, transform the scene to turn the shape to a circle
        if (_nodeShapeToCircle.IsIdentity == false)
        {
            hitBegin = _nodeShapeToCircle.Transform(hitBegin);
            hitEnd = _nodeShapeToCircle.Transform(hitEnd);
        }

        bool isHit = false;

        // Hit-test the big node
        double bigRadius = _radius * bigNode.PressureFactor;
        Vector nearest = GetNearest(hitBegin, hitEnd);
        if (nearest.LengthSquared <= (bigRadius * bigRadius))
        {
            isHit = true;
        }
        else if (quad.IsEmpty == false)
        {
            // Hit-test the other node
            Vector spineVector = smallNode.Position - bigNode.Position;
            if (_nodeShapeToCircle.IsIdentity == false)
            {
                spineVector = _nodeShapeToCircle.Transform(spineVector);
            }
            double smallRadius = _radius * smallNode.PressureFactor;
            nearest = GetNearest(hitBegin - spineVector, hitEnd - spineVector);
            if ((nearest.LengthSquared <= (smallRadius * smallRadius)) || HitTestQuadSegment(quad, hitBeginPoint, hitEndPoint))
            {
                isHit = true;
            }
        }

        return isHit;
    }

    /// <summary>
    /// Hit-tests a stroke segment defined by two nodes against another stroke segment.
    /// </summary>
    /// <param name="beginNode">Begin node of the stroke segment to hit-test. Can be empty (none)</param>
    /// <param name="endNode">End node of the stroke segment</param>
    /// <param name="quad">Pre-computed quadrangle connecting the two nodes.
    /// Can be empty if the begion node is empty or when one node is entirely inside the other</param>
    /// <param name="hitContour">a collection of basic segments outlining the hitting contour</param>
    /// <returns>true if the contours intersect or overlap</returns>
    internal override bool HitTest(
        in StrokeNodeData beginNode, in StrokeNodeData endNode, Quad quad, IEnumerable<ContourSegment> hitContour)
    {
        StrokeNodeData bigNode, smallNode;
        double bigRadiusSquared, smallRadiusSquared = 0;
        Vector spineVector;
        if (beginNode.IsEmpty || (quad.IsEmpty && (endNode.PressureFactor > beginNode.PressureFactor)))
        {
            // Need to test one node only
            bigNode = endNode;
            smallNode = StrokeNodeData.Empty;
            spineVector = new Vector();
        }
        else
        {
            // In this case the size doesn't matter.
            bigNode = beginNode;
            smallNode = endNode;

            smallRadiusSquared = _radius * smallNode.PressureFactor;
            smallRadiusSquared *= smallRadiusSquared;

            // Find position of smallNode relative to the bigNode.
            spineVector = smallNode.Position - bigNode.Position;
            // If the node shape is an ellipse, transform the scene to turn the shape to a circle
            if (_nodeShapeToCircle.IsIdentity == false)
            {
                spineVector = _nodeShapeToCircle.Transform(spineVector);
            }
        }

        bigRadiusSquared = _radius * bigNode.PressureFactor;
        bigRadiusSquared *= bigRadiusSquared;

        bool isHit = false;

        // When hit-testing a contour against another contour, like in this case,
        // the default implementation checks whether any edge (segment) of the hitting
        // contour intersects with the contour of the ink segment. But this doesn't cover
        // the case when the ink segment is entirely inside of the hitting segment.
        // The bool variable isInside is used here to track that case. It answers the question
        // 'Is ink contour inside if the hitting contour?'. It's initialized to 'true"
        // and then verified for each edge of the hitting contour until there's a hit or
        // until it's false.
        bool isInside = true;

        foreach (ContourSegment hitSegment in hitContour)
        {
            if (hitSegment.IsArc)
            {
                // ISSUE-2004/06/15-vsmirnov - ellipse vs arc hit-testing is not implemented
                // and currently disabled in ErasingStroke
            }
            else
            {
                // Find position of the hitting segment relative to bigNode transformed to circle.
                Vector hitBegin = hitSegment.Begin - bigNode.Position;
                Vector hitEnd = hitBegin + hitSegment.Vector;
                if (_nodeShapeToCircle.IsIdentity == false)
                {
                    hitBegin = _nodeShapeToCircle.Transform(hitBegin);
                    hitEnd = _nodeShapeToCircle.Transform(hitEnd);
                }

                // Hit-test the big node
                Vector nearest = GetNearest(hitBegin, hitEnd);
                if (nearest.LengthSquared <= bigRadiusSquared)
                {
                    isHit = true;
                    break;
                }

                // Hit-test the other node
                if (quad.IsEmpty == false)
                {
                    nearest = GetNearest(hitBegin - spineVector, hitEnd - spineVector);
                    if ((nearest.LengthSquared <= smallRadiusSquared) ||
                        HitTestQuadSegment(quad, hitSegment.Begin, hitSegment.End))
                    {
                        isHit = true;
                        break;
                    }
                }

                // While there's still a chance to find the both nodes inside the hitting contour,
                // continue checking on position of the endNode relative to the edges of the hitting contour.
                if (isInside &&
                    (WhereIsVectorAboutVector(endNode.Position - hitSegment.Begin, hitSegment.Vector) != HitResult.Right))
                {
                    isInside = false;
                }
            }
        }

        return (isHit || isInside);
    }

    /// <summary>
    /// Cut-test ink segment defined by two nodes and a connecting quad against a linear segment
    /// </summary>
    /// <param name="beginNode">Begin node of the ink segment</param>
    /// <param name="endNode">End node of the ink segment</param>
    /// <param name="quad">Pre-computed quadrangle connecting the two ink nodes</param>
    /// <param name="hitBeginPoint">egin point of the hitting segment</param>
    /// <param name="hitEndPoint">End point of the hitting segment</param>
    /// <returns>Exact location to cut at represented by StrokeFIndices</returns>
    internal override StrokeFIndices CutTest(
        in StrokeNodeData beginNode, in StrokeNodeData endNode, Quad quad, Point hitBeginPoint, Point hitEndPoint)
    {
        // Compute the positions of the involved points relative to the endNode.
        Vector spineVector = beginNode.IsEmpty ? new Vector(0, 0) : (beginNode.Position - endNode.Position);
        Vector hitBegin = hitBeginPoint - endNode.Position;
        Vector hitEnd = hitEndPoint - endNode.Position;

        // If the node shape is an ellipse, transform the scene to turn the shape to a circle
        if (_nodeShapeToCircle.IsIdentity == false)
        {
            spineVector = _nodeShapeToCircle.Transform(spineVector);
            hitBegin = _nodeShapeToCircle.Transform(hitBegin);
            hitEnd = _nodeShapeToCircle.Transform(hitEnd);
        }

        StrokeFIndices result = StrokeFIndices.Empty;

        // Hit-test the end node
        double beginRadius = 0, endRadius = _radius * endNode.PressureFactor;
        Vector nearest = GetNearest(hitBegin, hitEnd);
        if (nearest.LengthSquared <= (endRadius * endRadius))
        {
            result.EndFIndex = StrokeFIndices.AfterLast;
            result.BeginFIndex = beginNode.IsEmpty ? StrokeFIndices.BeforeFirst : 1;
        }
        if (beginNode.IsEmpty == false)
        {
            // Hit-test the first node
            beginRadius = _radius * beginNode.PressureFactor;
            nearest = GetNearest(hitBegin - spineVector, hitEnd - spineVector);
            if (nearest.LengthSquared <= (beginRadius * beginRadius))
            {
                result.BeginFIndex = StrokeFIndices.BeforeFirst;
                if (!DoubleUtil.AreClose(result.EndFIndex, StrokeFIndices.AfterLast))
                {
                    result.EndFIndex = 0;
                }
            }
        }

        // If both nodes are hit or nothing is hit at all, return.
        if (result.IsFull || quad.IsEmpty
                          || (result.IsEmpty && (HitTestQuadSegment(quad, hitBeginPoint, hitEndPoint) == false)))
        {
            return result;
        }

        // Find out whether the {begin, end} segment intersects with the contour
        // of the stroke segment {_lastNode, _thisNode}, and find the lower index
        // of the fragment to cut out.
        if (!DoubleUtil.AreClose(result.BeginFIndex, StrokeFIndices.BeforeFirst))
        {
            result.BeginFIndex = ClipTest(-spineVector, beginRadius, endRadius, hitBegin - spineVector, hitEnd - spineVector);
        }

        if (!DoubleUtil.AreClose(result.EndFIndex, StrokeFIndices.AfterLast))
        {
            result.EndFIndex = 1 - ClipTest(spineVector, endRadius, beginRadius, hitBegin, hitEnd);
        }

        if (IsInvalidCutTestResult(result))
        {
            return StrokeFIndices.Empty;
        }

        return result;
    }

    /// <summary>
    /// CutTest an inking StrokeNode segment (two nodes and a connecting quadrangle) against a hitting contour
    /// (represented by an enumerator of Contoursegments).
    /// </summary>
    /// <param name="beginNode">The begin StrokeNodeData</param>
    /// <param name="endNode">The end StrokeNodeData</param>
    /// <param name="quad">Connecing quadrangle between the begin and end inking node</param>
    /// <param name="hitContour">The hitting ContourSegments</param>
    /// <returns>StrokeFIndices representing the location for cutting</returns>
    internal override StrokeFIndices CutTest(
        in StrokeNodeData beginNode, in StrokeNodeData endNode, Quad quad, IEnumerable<ContourSegment> hitContour)
    {
        // Compute the positions of the beginNode relative to the endNode.
        Vector spineVector = beginNode.IsEmpty ? new Vector(0, 0) : (beginNode.Position - endNode.Position);
        // If the node shape is an ellipse, transform the scene to turn the shape to a circle
        if (_nodeShapeToCircle.IsIdentity == false)
        {
            spineVector = _nodeShapeToCircle.Transform(spineVector);
        }

        double beginRadius = 0, endRadius;
        double beginRadiusSquared = 0, endRadiusSquared;

        endRadius = _radius * endNode.PressureFactor;
        endRadiusSquared = endRadius * endRadius;
        if (beginNode.IsEmpty == false)
        {
            beginRadius = _radius * beginNode.PressureFactor;
            beginRadiusSquared = beginRadius * beginRadius;
        }

        bool isInside = true;
        StrokeFIndices result = StrokeFIndices.Empty;

        foreach (ContourSegment hitSegment in hitContour)
        {
            if (hitSegment.IsArc)
            {
                // ISSUE-2004/06/15-vsmirnov - ellipse vs arc hit-testing is not implemented
                // and currently disabled in ErasingStroke
            }
            else
            {
                Vector hitBegin = hitSegment.Begin - endNode.Position;
                Vector hitEnd = hitBegin + hitSegment.Vector;

                // If the node shape is an ellipse, transform the scene to turn
                // the shape into circle.
                if (_nodeShapeToCircle.IsIdentity == false)
                {
                    hitBegin = _nodeShapeToCircle.Transform(hitBegin);
                    hitEnd = _nodeShapeToCircle.Transform(hitEnd);
                }

                bool isHit = false;

                // Hit-test the end node
                Vector nearest = GetNearest(hitBegin, hitEnd);
                if (nearest.LengthSquared < endRadiusSquared)
                {
                    isHit = true;
                    if (!DoubleUtil.AreClose(result.EndFIndex, StrokeFIndices.AfterLast))
                    {
                        result.EndFIndex = StrokeFIndices.AfterLast;
                        if (beginNode.IsEmpty)
                        {
                            result.BeginFIndex = StrokeFIndices.BeforeFirst;
                            break;
                        }
                        if (DoubleUtil.AreClose(result.BeginFIndex, StrokeFIndices.BeforeFirst))
                        {
                            break;
                        }
                    }
                }

                if ((beginNode.IsEmpty == false) && (!isHit || !DoubleUtil.AreClose(result.BeginFIndex, StrokeFIndices.BeforeFirst)))
                {
                    // Hit-test the first node
                    nearest = GetNearest(hitBegin - spineVector, hitEnd - spineVector);
                    if (nearest.LengthSquared < beginRadiusSquared)
                    {
                        isHit = true;
                        if (!DoubleUtil.AreClose(result.BeginFIndex, StrokeFIndices.BeforeFirst))
                        {
                            result.BeginFIndex = StrokeFIndices.BeforeFirst;
                            if (DoubleUtil.AreClose(result.EndFIndex, StrokeFIndices.AfterLast))
                            {
                                break;
                            }
                        }
                    }
                }

                // If both nodes are hit or nothing is hit at all, return.
                if (beginNode.IsEmpty || (!isHit && (quad.IsEmpty ||
                                                     (HitTestQuadSegment(quad, hitSegment.Begin, hitSegment.End) == false))))
                {
                    if (isInside && (WhereIsVectorAboutVector(
                            endNode.Position - hitSegment.Begin, hitSegment.Vector) != HitResult.Right))
                    {
                        isInside = false;
                    }
                    continue;
                }

                isInside = false;

                // Calculate the exact locations to cut.
                CalculateCutLocations(spineVector, hitBegin, hitEnd, endRadius, beginRadius, ref result);

                if (result.IsFull)
                {
                    break;
                }
            }
        }

        //
        if (!result.IsFull)
        {
            if (isInside == true)
            {
                System.Diagnostics.Debug.Assert(result.IsEmpty);
                result = StrokeFIndices.Full;
            }
            else if ((DoubleUtil.AreClose(result.EndFIndex, StrokeFIndices.BeforeFirst)) && (!DoubleUtil.AreClose(result.BeginFIndex, StrokeFIndices.AfterLast)))
            {
                result.EndFIndex = StrokeFIndices.AfterLast;
            }
            else if ((DoubleUtil.AreClose(result.BeginFIndex,StrokeFIndices.AfterLast)) && (!DoubleUtil.AreClose(result.EndFIndex, StrokeFIndices.BeforeFirst)))
            {
                result.BeginFIndex = StrokeFIndices.BeforeFirst;
            }
        }

        if (IsInvalidCutTestResult(result))
        {
            return StrokeFIndices.Empty;
        }

        return result;
    }

    /// <summary>
    /// Clip-Testing a circluar inking segment against a linear segment.
    /// See http://tabletpc/longhorn/Specs/Rendering%20and%20Hit-Testing%20Ink%20in%20Avalon%20M11.doc section
    /// 5.4.4.14	Clip-Testing a Circular Inking Segment against a Linear Segment for details of the algorithm
    /// </summary>
    /// <param name="spineVector">Represent the spine of the inking segment pointing from the beginNode to endNode</param>
    /// <param name="beginRadius">Radius of the beginNode</param>
    /// <param name="endRadius">Radius of the endNode</param>
    /// <param name="hitBegin">Hitting segment start point</param>
    /// <param name="hitEnd">Hitting segment end point</param>
    /// <returns>A double which represents the location for cutting</returns>
    private static double ClipTest(Vector spineVector, double beginRadius, double endRadius, Vector hitBegin, Vector hitEnd)
    {
        // First handle the special case when the spineVector is (0,0). In other words, this is the case
        // when the stylus stays at the the location but pressure changes.
        if (DoubleUtil.IsZero(spineVector.X) && DoubleUtil.IsZero(spineVector.Y))
        {
            System.Diagnostics.Debug.Assert(DoubleUtil.AreClose(beginRadius, endRadius) == false);

            Vector nearest = GetNearest(hitBegin, hitEnd);
            double radius;
            if (nearest.X == 0)
            {
                radius = Math.Abs(nearest.Y);
            }
            else if (nearest.Y == 0)
            {
                radius = Math.Abs(nearest.X);
            }
            else
            {
                radius = nearest.Length;
            }
            return AdjustFIndex((radius - beginRadius) / (endRadius - beginRadius));
        }

        // This change to ClipTest with a point if the two hitting segment are close enough.
        if (DoubleUtil.AreClose(hitBegin, hitEnd))
        {
            return ClipTest(spineVector, beginRadius, endRadius, hitBegin);
        }

        double findex;
        Vector hitVector = hitEnd - hitBegin;

        if (DoubleUtil.IsZero(Vector.Determinant(spineVector, hitVector)))
        {
            // hitVector and spineVector are parallel
            findex = ClipTest(spineVector, beginRadius, endRadius, GetNearest(hitBegin, hitEnd));
            System.Diagnostics.Debug.Assert(!double.IsNaN(findex));
        }
        else
        {
            // On the line defined by the segment find point P1Xp, the nearest to the beginNode.Position
            double x = GetProjectionFIndex(hitBegin, hitEnd);
            Vector P1Xp = hitBegin + (hitVector * x);
            if (P1Xp.LengthSquared < (beginRadius * beginRadius))
            {
                System.Diagnostics.Debug.Assert(DoubleUtil.IsBetweenZeroAndOne(x) == false);
                findex = ClipTest(spineVector, beginRadius, endRadius, (0 > x) ? hitBegin : hitEnd);
                System.Diagnostics.Debug.Assert(!double.IsNaN(findex));
            }
            else
            {
                // Find the projection point P of endNode.Position to the line (beginNode.Position, B).
                Vector P1P2p = spineVector + GetProjection(-spineVector, P1Xp - spineVector);

                //System.Diagnostics.Debug.Assert(false == DoubleUtil.IsZero(P1P2p.LengthSquared));
                //System.Diagnostics.Debug.Assert(false == DoubleUtil.IsZero(endRadius - beginRadius + P1P2p.Length));
                // There checks are here since if either fail no real solution can be caculated and we may
                // as well bail out now and save the caculations that are below.
                if (DoubleUtil.IsZero(P1P2p.LengthSquared) || DoubleUtil.IsZero(endRadius - beginRadius + P1P2p.Length))
                    return 1d;

                // Calculate the findex of the point to split the ink segment at.
                findex = (P1Xp.Length - beginRadius) / (endRadius - beginRadius + P1P2p.Length);
                System.Diagnostics.Debug.Assert(!double.IsNaN(findex));

                // Find the projection of the split point to the line of this segment.
                Vector S = spineVector * findex;

                double r = GetProjectionFIndex(hitBegin - S, hitEnd - S);

                // If the nearest point misses the segment, then find the findex
                // of the node nearest to the segment.
                if (false == DoubleUtil.IsBetweenZeroAndOne(r))
                {
                    findex = ClipTest(spineVector, beginRadius, endRadius, (0 > r) ? hitBegin : hitEnd);
                    System.Diagnostics.Debug.Assert(!double.IsNaN(findex));
                }
            }
        }
        return AdjustFIndex(findex);
    }

    /// <summary>
    /// Clip-Testing a circular inking segment again a hitting point.
    ///
    /// What need to find out a doulbe value s, which is between 0 and 1, such that
    ///     DistanceOf(hit - s*spine) = beginRadius + s * (endRadius - beginRadius)
    /// That is
    ///     (hit.X-s*spine.X)^2 + (hit.Y-s*spine.Y)^2 = [beginRadius + s*(endRadius-beginRadius)]^2
    /// Rearrange
    ///     A*s^2 + B*s + C = 0
    /// where the value of A, B and C are described in the source code.
    /// Solving for s:
    ///             s = (-B + sqrt(B^2-4A*C))/(2A)  or s = (-B - sqrt(B^2-4A*C))/(2A)
    /// The smaller value between 0 and 1 is the one we want and discard the other one.
    /// </summary>
    /// <param name="spine">Represent the spine of the inking segment pointing from the beginNode to endNode</param>
    /// <param name="beginRadius">Radius of the beginNode</param>
    /// <param name="endRadius">Radius of the endNode</param>
    /// <param name="hit">The hitting point</param>
    /// <returns>A double which represents the location for cutting</returns>
    private static double ClipTest(Vector spine, double beginRadius, double endRadius, Vector hit)
    {
        double radDiff = endRadius - beginRadius;
        double A = spine.X*spine.X + spine.Y*spine.Y - radDiff*radDiff;
        double B = -2.0f*(hit.X*spine.X + hit.Y * spine.Y + beginRadius*radDiff);
        double C = hit.X * hit.X + hit.Y * hit.Y - beginRadius * beginRadius;

        // There checks are here since if either fail no real solution can be caculated and we may
        // as well bail out now and save the caculations that are below.
        if (DoubleUtil.IsZero(A) || !DoubleUtil.GreaterThanOrClose(B*B, 4.0f*A*C))
            return 1d;

        double tmp = Math.Sqrt(B*B-4.0f * A * C);
        double s1 = (-B + tmp)/(2.0f * A);
        double s2 = (-B - tmp)/(2.0f * A);
        double findex;

        if (DoubleUtil.IsBetweenZeroAndOne(s1) && DoubleUtil.IsBetweenZeroAndOne(s1))
        {
            findex = Math.Min(s1, s2);
        }
        else if (DoubleUtil.IsBetweenZeroAndOne(s1))
        {
            findex = s1;
        }
        else if (DoubleUtil.IsBetweenZeroAndOne(s2))
        {
            findex = s2;
        }
        else
        {
            // There is still possiblity that value like 1.0000000000000402 is not considered
            // as "IsOne" by DoubleUtil class. We should be at either one of the following two cases:
            // 1. s1/s2 around 0 but not close enough (say -0.0000000000001)
            // 2. s1/s2 around 1 but not close enought (say 1.0000000000000402)

            if (s1 > 1d && s2 > 1d)
            {
                findex = 1d;
            }
            else if (s1 < 0d && s2 < 0d)
            {
                findex = 0d;
            }
            else
            {
                findex = Math.Abs(Math.Min(s1, s2) - 0d) < Math.Abs(Math.Max(s1, s2) - 1d) ? 0d : 1d;
            }
        }
        return AdjustFIndex(findex);
    }

    /// <summary>
    /// Helper function to find out the relative location of a segment {segBegin, segEnd} against
    /// a strokeNode (spine).
    /// </summary>
    /// <param name="spine">the spineVector of the StrokeNode</param>
    /// <param name="segBegin">Start position of the line segment</param>
    /// <param name="segEnd">End position of the line segment</param>
    /// <returns>HitResult</returns>
    private static HitResult WhereIsNodeAboutSegment(Vector spine, Vector segBegin, Vector segEnd)
    {
        HitResult whereabout = HitResult.Right;
        Vector segVector = segEnd - segBegin;

        if ((WhereIsVectorAboutVector(-segBegin, segVector) == HitResult.Left)
            && !DoubleUtil.IsZero(Vector.Determinant(spine, segVector)))
        {
            whereabout = HitResult.Left;
        }
        return whereabout;
    }

    /// <summary>
    /// Helper method to calculate the exact location to cut
    /// </summary>
    /// <param name="spineVector">Vector the relative location of the two inking nodes</param>
    /// <param name="hitBegin">the begin point of the hitting segment</param>
    /// <param name="hitEnd">the end point of the hitting segment</param>
    /// <param name="endRadius">endNode radius</param>
    /// <param name="beginRadius">beginNode radius</param>
    /// <param name="result">StrokeFIndices representing the location for cutting</param>
    private void CalculateCutLocations(
        Vector spineVector, Vector hitBegin, Vector hitEnd, double endRadius, double beginRadius, ref StrokeFIndices result)
    {
        // Find out whether the {hitBegin, hitEnd} segment intersects with the contour
        // of the stroke segment, and find the lower index of the fragment to cut out.
        if (!DoubleUtil.AreClose(result.EndFIndex, StrokeFIndices.AfterLast))
        {
            if (WhereIsNodeAboutSegment(spineVector, hitBegin, hitEnd) == HitResult.Left)
            {
                double findex = 1 - ClipTest(spineVector, endRadius, beginRadius, hitBegin, hitEnd);
                if (findex > result.EndFIndex)
                {
                    result.EndFIndex = findex;
                }
            }
        }

        // Find out whether the {hitBegin, hitEnd} segment intersects with the contour
        // of the stroke segment, and find the higher index of the fragment to cut out.
        if (!DoubleUtil.AreClose(result.BeginFIndex, StrokeFIndices.BeforeFirst))
        {
            hitBegin -= spineVector;
            hitEnd -= spineVector;
            if (WhereIsNodeAboutSegment(-spineVector, hitBegin, hitEnd) == HitResult.Left)
            {
                double findex = ClipTest(-spineVector, beginRadius, endRadius, hitBegin, hitEnd);
                if (findex < result.BeginFIndex)
                {
                    result.BeginFIndex = findex;
                }
            }
        }
    }

    private double _radius = 0;
    private Size   _radii;
    private Matrix _transform;
    private Matrix _nodeShapeToCircle;
    private Matrix _circleToNodeShape;
}